/*
 * Copyright (c) 2016, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
 /*****************************************************************************

 Application Name     -   Wi-Fi Doorlock Demo
 Application Overview -   
 
 Application Details  - 
 
 *****************************************************************************/
 
//*****************************************************************************
//
//! \addtogroup wifi_doorlock_app
//! @{
//
//*****************************************************************************

/* Standard includes                                                          */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <mqueue.h>
#include <time.h>

/* Hardware includes                                                          */
#include <ti/devices/cc32xx/inc/hw_types.h>
#include <ti/devices/cc32xx/inc/hw_ints.h>
#include <ti/devices/cc32xx/inc/hw_memmap.h>

/* Driverlib includes                                                         */
#include <ti/devices/cc32xx/driverlib/rom.h>
#include <ti/devices/cc32xx/driverlib/rom_map.h>
#include <ti/devices/cc32xx/driverlib/prcm.h>

/* SlNetSock includes (to enable portability to other interfaces like ETH)    */
#include <ti/drivers/net/wifi/slnetifwifi.h>

/* Common interface includes                                                  */
#include "platform.h"
#include "network_if.h"

/* TI-Drivers includes                                                        */
#include <ti/drivers/Power.h>
#include <ti/drivers/Timer.h>
#include <ti/display/Display.h>
#include <ti/sail/bmi160/bmi160.h>

/* Application includes                                                       */
#include "Board.h"
#include "pthread.h"

#include "wifi_doorlock_app.h"
#include "control_task.h"
#include "provisioning_task.h"
#include "cloud_ota_task.h"
#include "mqtt_client_task.h"
#include "motor_driver_if.h"
#include "ble_provisioning.h"
#include "bmi160_support.h"

extern void pushButtonInterruptHandler(uint_least8_t index);

#if (PB_HW_TIMERS == 1)
extern void holdTimerHandler(Timer_Handle handle);
extern void debounceTimerHandler(Timer_Handle handle);
#else
extern void holdTimerIntHandler(sigval val);
extern void debounceTimerIntHandler(sigval val);
#endif

/****************************************************************************************************************
                   LOCAL FUNCTION PROTOTYPES
****************************************************************************************************************/

/* Local functions used to initialize SW */
static int32_t initMCUDrivers(void);
static int32_t initNetworkInterface(void);
static int32_t initGlobalRTOSObjects(void);
static int32_t spawnApplicationThreads(void);

/* Local functions for starting tasks */
static int32_t BLE_start(void);
static int32_t Control_start(void);
static int32_t LocalNetwork_start(void);
static int32_t Mqtt_start(void);
static int32_t APSCProvisioning_start(void);
static int32_t Ota_start(void);
static int32_t Motor_start(void);
static int32_t Sensors_start(void);
static void    Buttons_start(void);

/* Functions for stopping tasks */
void Mqtt_stop();
void Motor_stop();
void Buttons_stop();

/****************************************************************************************************************
                   GLOBAL VARIABLES
****************************************************************************************************************/
Application_CB App_CB;
Display_Handle display;
mqd_t bleQueueRec;
mqd_t bleQueueSend;

const char *Roles[] = {"STA","STA","AP","P2P"};
const char *WlanStatus[] = {"DISCONNECTED","SCANING","CONNECTING","CONNECTED"};

static pthread_t g_spawn_thread            = (pthread_t) NULL;
static pthread_t g_local_network_thread    = (pthread_t) NULL;
static pthread_t g_provisioning_thread     = (pthread_t) NULL;
static pthread_t g_ble_thread              = (pthread_t) NULL;
static pthread_t g_mqtt_thread             = (pthread_t) NULL;
static pthread_t g_motor_thread            = (pthread_t) NULL;
static pthread_t g_ota_thread              = (pthread_t) NULL;
static pthread_t g_control_thread          = (pthread_t) NULL;
static pthread_t g_sensor_thread      = (pthread_t) NULL;

#if (PB_HW_TIMERS == 1)
extern Timer_Handle holdTimer;
extern Timer_Handle debounceTimer;
extern Timer_Params timerParams;
#else
extern timer_t holdTimer;
extern timer_t debounceTimer;
#endif

/****************************************************************************************************************
                 Local Functions
****************************************************************************************************************/
UART_Handle myuart;
typedef struct DisplayUart_Object
{
    UART_Handle       hUart;
    SemaphoreP_Handle mutex;
    char              *lineClearSeq;
} DisplayUart_Object, *DisplayUart_Handle;

static int32_t initMCUDrivers()
{
    int32_t ret = 0;

    /* Init pins used as GPIOs and SPI for communication between M4/NWP */
    GPIO_init();
    UART_init();
    SPI_init();
#if (PB_HW_TIMERS == 1)
    Timer_init();
#endif
#if (SENSORS == 1)
    /* Init I2C for communicating with digital sensors  */
    I2C_init();
#endif

    /* Configure default state of NPI control signals and LEDs */
   // GPIO_write(Board_RESET, 0);
   /// GPIO_write(Board_SRDY, 1);

    GPIO_write(Board_LED0, 0);
    GPIO_write(Board_LED1, 0);
    GPIO_write(Board_LED2, 0);

#ifdef DEBUG_PRINT
    /* Configure the UART for printing to terminal      */
    Display_init();
    display = Display_open(Display_Type_UART | Display_Type_HOST, NULL);
    if (display == NULL)
    {
        /* Failed to open display driver */
        ret = -1;
    }
    myuart=((DisplayUart_Object*)(display->object))->hUart;
#endif

    return ret;
}

static int32_t initNetworkInterface()
{
    /* Local vars */
    int32_t ret = 0;
    pthread_attr_t pAttrs_spawn;
    struct sched_param priParam;

    /* Initialize SlNetSock layer with CC3x20 interface             (consider moving to network connection thread)          */
    SlNetIf_init(0);
    SlNetIf_add(SLNETIF_ID_1, "CC3220",
                (const SlNetIf_Config_t *) &SlNetIfConfigWifi,
                SLNET_IF_WIFI_PRIO);

    SlNetSock_init(0);
    SlNetUtil_init(0);

    /* Create the sl_Task  for handling async events from SimpleLink host driver                                            */
    pthread_attr_init(&pAttrs_spawn);
    priParam.sched_priority = SPAWN_TASK_PRIORITY;
    ret = pthread_attr_setschedparam(&pAttrs_spawn, &priParam);
    ret |= pthread_attr_setstacksize(&pAttrs_spawn, TASK_STACK_SIZE);
    ret |= pthread_attr_setdetachstate(&pAttrs_spawn, PTHREAD_CREATE_DETACHED);

    ret = pthread_create(&g_spawn_thread, &pAttrs_spawn, sl_Task, NULL);

#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display, 0, 0,
                "\t [Main Thread] Could not create SimpleLink driver task");
    }
#endif

    return ret;
}

static int32_t initGlobalRTOSObjects()
{
    /* Local vars */
    int32_t ret = -1;

    mq_attr attr;
    unsigned mode = 0;

    /* sync object for inter-thread communication                             */
    attr.mq_maxmsg = 10;
    attr.mq_msgsize = sizeof(struct controlMsg);

    App_CB.controlQueue = mq_open("Reset Request Queue", O_CREAT, mode, &attr);
    if (App_CB.controlQueue == NULL)
    {
#ifdef DEBUG_PRINT
        Display_printf(
                display, 0, 0,
                "\t [Main Thread] Unable to create the reset request queue");
#endif
        return ret;
    }

    /* Create RTOS Queue for BLE Provisioning */
    attr.mq_flags = 0;
    attr.mq_maxmsg = 64;
    attr.mq_msgsize = sizeof(uint32_t);
    attr.mq_curmsgs = 0;

    bleQueueRec = mq_open("BLEProvision", O_RDWR | O_CREAT, 0664, &attr);
    bleQueueSend = mq_open("BLEProvision", O_RDWR | O_CREAT | O_NONBLOCK, 0664,
                           &attr);

    /* Initializing Network IF queue */
    attr.mq_flags = 0;
    attr.mq_maxmsg = 10;
    attr.mq_msgsize = sizeof(struct networkIfMsg);
    attr.mq_curmsgs = 0;

    App_CB.networkIfQueue = mq_open("NetworkIFQueue", O_CREAT, mode, &attr);
    if(App_CB.networkIfQueue == NULL)
    {
#ifdef DEBUG_PRINT
        Display_printf(
                display, 0, 0,
                "\t [Main Thread] Unable to create the network interface queue");
#endif
        return ret;
    }

    /* Create sync objects for signaling between tasks */
    ret = sem_init(&App_CB.controlReady, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Unable to create the control thread ready semaphore");
    }
#endif

    ret = sem_init(&App_CB.localNetworkReady, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Unable to create the local network thread ready semaphore");
    }
#endif

    ret = sem_init(&App_CB.connectionAsyncEvent, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display, 0, 0,
                "\t [Main Thread] Unable to create the connection semaphore");
    }
#endif

    ret = sem_init(&App_CB.provReady, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Unable to create the provisioning thread ready semaphore");
    }
#endif

    ret = sem_init(&App_CB.connectionDoneSignal, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Unable to create the connection done semaphore");
    }
#endif

    ret = sem_init(&App_CB.motorReady, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Unable to create the motor thread ready semaphore");
    }
#endif

    ret = sem_init(&App_CB.sensorReady, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Unable to create the sensor thread ready semaphore");
    }
#endif

    /* The semaphore is used by the bmi160 interrupt handler task
     * the semaphore is normally posted by bmi160 interrupt(watermark)
     * The interrupt handler task waits on this semaphore.
     */
    ret = sem_init(&App_CB.bmi160Sem, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        /* sem_init() failed */
        Display_printf(display, 0, 0, "\t [Main Thread] Unable to create bmi160 Semaphore");
    }
#endif

    /* The semaphore is used by the display task
     * display task pushes the sensor data out to console
     */
    ret = sem_init(&App_CB.sensorDisplaySem, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        /* sem_init() failed */
        Display_printf(display, 0, 0, "\t [Main Thread] Unable to create sensor display Semaphore");
    }
#endif

    ret = sem_init(&App_CB.mqttReady, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Unable to create the mqtt thread ready semaphore");
    }
#endif

    ret = sem_init(&App_CB.otaReady, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Unable to create the OTA thread ready semaphore");
    }
#endif

    ret = sem_init(&App_CB.bleReady, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Unable to create the HTTP Server thread ready semaphore");
    }
#endif

    ret = sem_init(&App_CB.startBarrier, 0, 0);
#ifdef DEBUG_PRINT
    if (ret != 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Unable to create the thread start barrier semaphore");
    }
#endif

    return ret;
}

static int32_t spawnApplicationThreads()
{
    int32_t ret = 0;

    /* Control Task */
    ret |= Control_start();

    /* Local Network Connection Task */
    ret |= LocalNetwork_start();

    /* Provisioning Task  */
    ret |= APSCProvisioning_start();

    /* BLE Communication Task */
    ret |= BLE_start();

    /* OTA Update Task */
    ret |= Ota_start();

    /* MQTT Tasks */
    ret |= Mqtt_start();

    /* Motor (PWM) Task */
    ret |= Motor_start();

    /* Sensor reading task */
    ret |= Sensors_start();

    /* Enable push-button interrupts */
    Buttons_start();

#ifdef DEBUG_PRINT
    if (ret < 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Error - One or more tasks failed to initialize");
    }
#endif

    return ret;
}

//*****************************************************************************
//!
//! Motor Start - Initialize and create all the items required to run the motor
//!
//! \param  none
//!
//! \return None
//!
//*****************************************************************************
static int32_t Motor_start(void)
{
#if (MOTOR == 1)
    pthread_attr_t pAttrs;
    struct sched_param priParam;
    int32_t retc = 0;
    mq_attr attr;
    unsigned mode = 0;

    /* sync object for inter-thread communication                             */
    attr.mq_maxmsg = 10;
    attr.mq_msgsize = sizeof(struct motorMsg);
    App_CB.motorQueue = mq_open("Motor Queue", O_CREAT, mode, &attr);

    if (App_CB.motorQueue == NULL)
    {
#ifdef DEBUG_PRINT
        Display_printf(display, 0, 0,
                       "\t [Main Thread] Motor Message Queue create fail");
#endif
        return -1;
    }

    /* Set priority and stack size attributes                                 */
    pthread_attr_init(&pAttrs);
    priParam.sched_priority = 2;
    retc = pthread_attr_setschedparam(&pAttrs, &priParam);
    retc |= pthread_attr_setstacksize(&pAttrs, MOTOR_TASK_SIZE);
    retc |= pthread_attr_setdetachstate(&pAttrs, PTHREAD_CREATE_DETACHED);

    if (retc != 0)
    {
#ifdef DEBUG_PRINT
        Display_printf(display, 0, 0,
                       "\t [Main Thread] Motor thread create fail");
#endif
        return retc;
    }

    retc = pthread_create(&g_motor_thread, &pAttrs, MotorDriverControl, NULL);
    if (retc != 0)
    {
#ifdef DEBUG_PRINT
        Display_printf(display, 0, 0,
                       "\t [Main Thread] Motor thread create fail");
#endif
        return retc;
    }

    return retc;
#else
    sem_post(&App_CB.motorReady);
    return 0;
#endif
}

void Motor_stop()
{
#if (MOTOR == 1)
    /* Close message queue for signaling MQTT events */
    mq_close(App_CB.motorQueue);
    App_CB.motorQueue = NULL;

    /* Put motor driver to sleep */
    GPIO_write(Board_GPIO_nSLEEP, 0);

#ifdef DEBUG_PRINT
    Display_printf(display, 0, 0, "\n\r[Main Thread] Motor stop completed");
#endif
#endif
}

//*****************************************************************************
//!
//! MQTT Start - Initialize and create all the items required to run the MQTT
//! protocol
//!
//! \param  none
//!
//! \return None
//!
//*****************************************************************************
static int32_t Mqtt_start()
{
#if (MQTT == 1)
    int32_t threadArg = 100;
    pthread_attr_t pAttrs;
    struct sched_param priParam;
    int32_t retc = 0;
    mq_attr attr;
    unsigned mode = 0;

    /* sync object for inter-thread communication                             */
    attr.mq_maxmsg = 10;
    attr.mq_msgsize = sizeof(struct msgQueue);
    App_CB.mqttQueue = mq_open("Queue", O_CREAT, mode, &attr);

    if (App_CB.mqttQueue == NULL)
    {
#ifdef DEBUG_PRINT
        Display_printf(display, 0, 0,
                       "\t [Main Thread] MQTT Message Queue create fail");
#endif
        return -1;
    }

    /* Set priority and stack size attributes                                 */
    pthread_attr_init(&pAttrs);
    priParam.sched_priority = 3;
    retc = pthread_attr_setschedparam(&pAttrs, &priParam);
    retc |= pthread_attr_setstacksize(&pAttrs, MQTT_TASK_SIZE);
    retc |= pthread_attr_setdetachstate(&pAttrs, PTHREAD_CREATE_DETACHED);

    if (retc != 0)
    {
#ifdef DEBUG_PRINT
        Display_printf(display, 0, 0,
                       "\t [Main Thread] MQTT thread create fail");
#endif
        return retc;
    }

    retc = pthread_create(&g_mqtt_thread, &pAttrs, MqttClient,
                          (void *) &threadArg);
    if (retc != 0)
    {
#ifdef DEBUG_PRINT
        Display_printf(display, 0, 0,
                       "\t [Main Thread] MQTT thread create fail");
#endif
        return retc;
    }

    return retc;
#else
    sem_post(&App_CB.mqttReady);
    return 0;
#endif
}

//*****************************************************************************
//!
//! MQTT Stop - Close the client instance and free all the items required to
//! run the MQTT protocol
//!
//! \param  none
//!
//! \return None
//!
//*****************************************************************************

void Mqtt_stop()
{
#if(MQTT == 1)
    struct msgQueue queueElement;
    struct msgQueue queueElemRecv;

    queueElement.event = THREAD_TERMINATE_REQ;
    queueElement.msgPtr = NULL;

    /* write message indicating publish message                               */
    if (MQTT_SendMsgToQueue(&queueElement))
    {
#ifdef DEBUG_PRINT
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Queue is full, throw first msg and send the new one");
#endif
        mq_receive(App_CB.mqttQueue, (char*) &queueElemRecv,
                   sizeof(struct msgQueue), NULL);
        MQTT_SendMsgToQueue(&queueElement);
    }

    while (GET_STATUS_BIT(App_CB.status, AppStatusBits_MqttStopInProgress))
    {
        sleep(1);
    }

#ifdef DEBUG_PRINT
    Display_printf(display, 0, 0, "[Main Thread] Closing MQTT queue");
#endif

    /* Close message queue for signaling MQTT events */
    mq_close(App_CB.mqttQueue);
    App_CB.mqttQueue = NULL;

#ifdef DEBUG_PRINT
    Display_printf(display, 0, 0, "[Main Thread] Stopping network processor");
#endif

    sl_Stop(SL_STOP_TIMEOUT);
#ifdef DEBUG_PRINT
    Display_printf(display, 0, 0, "[Main Thread] Client stop completed");
#endif
#endif
}

//*****************************************************************************
//!
//! Local Network Connection Start - Initialize and create all items required
//! to run the local network connection task
//!
//! \param  none
//!
//! \return None
//!
//*****************************************************************************
static int32_t LocalNetwork_start()
{
    int32_t retc = 0;

#if (NETWORK_IF == 1)
    /* Thread creation variables                                                        */
    pthread_attr_t pAttrs;
    struct sched_param priParam;

    /* Provisioning Task  */
    pthread_attr_init(&pAttrs);
    priParam.sched_priority = 10;
    retc = pthread_attr_setschedparam(&pAttrs, &priParam);
    retc |= pthread_attr_setstacksize(&pAttrs, LOCAL_NETWORK_TASK_STACK_SIZE);

#ifdef DEBUG_PRINT
    if (retc < 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Error - Unable to configure localNetworkTask thread parameters");
    }
#endif

    retc = pthread_create(&g_local_network_thread, &pAttrs, localNetworkTask,
                          NULL);
#ifdef DEBUG_PRINT
    if (retc < 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Error - Unable to create localNetworkTask thread");
    }
#endif
#else
    sem_post(&App_CB.localNetworkReady);
#endif

    return retc;
}

//*****************************************************************************
//!
//! Provisioning Start - Initialize and create all the items required to run
//! the AP/SC provisioning task
//!
//! \param  none
//!
//! \return None
//!
//*****************************************************************************
static int32_t APSCProvisioning_start()
{
    int32_t retc = 0;

#if (APSC_PROVISIONING == 1)

    /* Thread creation variables                                                        */
    pthread_attr_t pAttrs;
    struct sched_param priParam;

    /* Provisioning Task  */
    pthread_attr_init(&pAttrs);
    priParam.sched_priority = 9;
    retc = pthread_attr_setschedparam(&pAttrs, &priParam);
    retc |= pthread_attr_setstacksize(&pAttrs, PROVISIONING_TASK_SIZE);
#ifdef DEBUG_PRINT
    if (retc < 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Error - Unable to configure provisioningTask thread parameters");
    }
#endif

    retc = pthread_create(&g_provisioning_thread, &pAttrs, provisioningTask,
                          NULL);
#ifdef DEBUG_PRINT
    if (retc < 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Error - Unable to create provisioningTask thread");
    }
#endif

#else
    sem_post(&App_CB.provReady);
#endif
    return retc;
}

//*****************************************************************************
//!
//! BLE Start - Initialize and create all the items required to run the SAP
//!             thread used to communicate with the BLE device for provisioning
//!
//! \param  none
//!
//! \return None
//!
//*****************************************************************************
static int32_t BLE_start(void)
{
    int32_t retc = 0;

#if (BLE_PROVISIONING == 1)
    /* Thread creation variables                                                        */
    pthread_attr_t pAttrs;
    struct sched_param priParam;
    int detachState;

    /* Provisioning Task  */
    pthread_attr_init(&pAttrs);
    priParam.sched_priority = 8;
    detachState = PTHREAD_CREATE_DETACHED;
    retc = pthread_attr_setdetachstate(&pAttrs, detachState);
    retc = pthread_attr_setschedparam(&pAttrs, &priParam);
    retc |= pthread_attr_setstacksize(&pAttrs, BLE_TASK_SIZE);

#ifdef DEBUG_PRINT
    if (retc < 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Error - Unable to configure bleThread parameters");
    }
#endif

    retc = pthread_create(&g_ble_thread, &pAttrs, bleThread, NULL);
#ifdef DEBUG_PRINT
    if (retc < 0)
    {
        Display_printf(display, 0, 0,
                       "\t [Main Thread] Error - Unable to create  bleThread");
    }
#endif
#else
    sem_post(&App_CB.bleReady);
#endif
    return retc;
}

//*****************************************************************************
//!
//! OTA Start - Initialize and create all the items required to run
//! the task for handling over-the-air updates (cloud)
//!
//! \param  none
//!
//! \return None
//!
//*****************************************************************************
static int32_t Ota_start()
{
    int32_t retc = 0;

#if (OTA == 1)

    /* Thread creation variables                                                        */
    pthread_attr_t pAttrs;
    struct sched_param priParam;

    pthread_attr_init(&pAttrs);
    priParam.sched_priority = 5;
    retc = pthread_attr_setschedparam(&pAttrs, &priParam);
    retc |= pthread_attr_setstacksize(&pAttrs, OTA_TASK_SIZE);
#ifdef DEBUG_PRINT
    if (retc < 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Error - Unable to configure OtaTask thread parameters");
    }
#endif

    retc = pthread_create(&g_ota_thread, &pAttrs, OtaTask, NULL);
#ifdef DEBUG_PRINT
    if (retc < 0)
    {
        Display_printf(
                display, 0, 0,
                "\t [Main Thread] Error - Unable to create OTATask thread");
    }
#endif

#else
    sem_post(&App_CB.otaReady);
#endif

    return retc;
}

//*****************************************************************************
//!
//! Control Thread Start - Spawn the control thread
//!
//! \param  none
//!
//! \return None
//!
//*****************************************************************************
static int32_t Control_start()
{
    int32_t retc = 0;

#if (CONTROL == 1)
    /* Thread creation variables                                                        */
    pthread_attr_t pAttrs;
    struct sched_param priParam;

    pthread_attr_init(&pAttrs);
    priParam.sched_priority = 11;
    retc = pthread_attr_setschedparam(&pAttrs, &priParam);
    retc |= pthread_attr_setstacksize(&pAttrs, CONTROL_TASK_SIZE);
#ifdef DEBUG_PRINT
    if (retc < 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Main Thread] Error - Unable to configure ControlTask thread parameters");
    }
#endif

    retc = pthread_create(&g_control_thread, &pAttrs, ControlTask, NULL);
#ifdef DEBUG_PRINT
    if (retc < 0)
    {
        Display_printf(
                display, 0, 0,
                "\t [Main Thread] Error - Unable to create ControlTask thread");
    }
#endif
#else
    sem_post(&App_CB.controlReady);
#endif

    return retc;
}

//*****************************************************************************
//!
//! Sensors Start - This function used for initialize the sensor in 9DoF fifo
//! mode.
//!
//! \param  none
//!
//! \return None
//!
//*****************************************************************************
static int32_t Sensors_start()
{
    int32_t retc = 0;

#if (SENSORS == 1)
    /* Thread creation variables                                                        */
    pthread_attr_t pAttrs;
    struct sched_param priParam;
    mq_attr attr;
    unsigned mode = 0;

    /* sync object for inter-thread communication                             */
    attr.mq_maxmsg = 10;
    attr.mq_msgsize = sizeof(BMI160Event);
    App_CB.bmi160Queue = mq_open("Sensor Queue", O_CREAT, mode, &attr);

    if(App_CB.bmi160Queue == NULL)
    {
#ifdef DEBUG_PRINT
        Display_printf(display, 0, 0,
                       "\t [Main Thread] Sensor message queue create fail");
#endif
    }


    pthread_attr_init(&pAttrs);
    /* Set priority and stack size attributes */
    pthread_attr_setstacksize(&pAttrs, BMI_TASK_STACK_SIZE);
    priParam.sched_priority = 1;
    pthread_attr_setschedparam(&pAttrs, &priParam);
    retc = pthread_create(&g_sensor_thread, &pAttrs, bmiTask, NULL);
    if (retc != 0)
    {
        /* pthread_create() failed */
        Display_printf(display, 0, 0,
                       "BMI interrupt handler Task creation failed");
    }

#else
    sem_post(&App_CB.sensorReady);
#endif

    return retc;
}

void Buttons_start()
{
#if (PUSH_BUTTONS == 1)
    GPIO_setCallback(Board_BUTTON1, pushButtonInterruptHandler);
    GPIO_enableInt(Board_BUTTON1); // SW3 (Rev. A)

    Platform_TimerInit(debounceTimerIntHandler, &debounceTimer);
    Platform_TimerInit(holdTimerIntHandler, &holdTimer);
#endif
}

void Buttons_stop()
{
#if (PUSH_BUTTONS == 1)
    /* Disable the button interrupt */
    GPIO_disableInt(Board_BUTTON1); // SW3 (Rev. A)

    Platform_TimerDeinit(holdTimer);
    Platform_TimerDeinit(debounceTimer);
#endif
}

/****************************************************************************************************************
                 Main Thread
****************************************************************************************************************/
void *mainThread(void * args)
{
    /* Return code variable (for error checking)                                        */
    int32_t retc = 0;
    uint8_t threadCount;

    CLR_STATUS_BIT_ALL(App_CB.status);

    /* Initialize peripheral driver interfaces                                          */
    retc = initMCUDrivers();
    /* Initialize networking layer for Wi-Fi and start async task for Wi-Fi host driver */
    retc |= initNetworkInterface();
    /* Initialize RTOS objects for signaling between tasks                              */
    retc |= initGlobalRTOSObjects();
    /* Spawn other threads */
    retc |= spawnApplicationThreads();

    if (retc < 0)
    {
        Display_printf(
                display,
                0,
                0,
                "[Main Thread] Error - Failed to initialize drivers, RTOS objects, or additional threads");
        while (1);
    }

    /* Wait for spawned threads to be ready                                             */
    sem_wait(&App_CB.localNetworkReady);
    sem_wait(&App_CB.provReady);
    sem_wait(&App_CB.otaReady);
    sem_wait(&App_CB.motorReady);
    sem_wait(&App_CB.sensorReady);
    sem_wait(&App_CB.mqttReady);
    sem_wait(&App_CB.controlReady);
    sem_wait(&App_CB.bleReady);

    /* Unblock all spawned threads                                                      */
    for (threadCount = 0; threadCount < NUM_TASKS_ENABLED; threadCount++)
    {
        sem_post(&App_CB.startBarrier);
    }

    pthread_detach(pthread_self());
    pthread_exit(0);

    return (0);
}

//*****************************************************************************
//
// Close the Doxygen group.
//! @}
//
//*****************************************************************************
